﻿namespace WinRT.Triggers
{
    using System;
    using System.Linq;

    using Windows.UI.Xaml;
    using Windows.UI.Xaml.Controls;
    using Windows.UI.Xaml.Media;

    /// <summary>
    /// The custom visual state manager.
    /// </summary>
    internal class CustomVisualStateManager : VisualStateManager
    {
        /// <summary>
        /// The custom visual state manager instance.
        /// </summary>
        private static CustomVisualStateManager instance = new CustomVisualStateManager();

        #region Public Methods and Operators

        /// <summary>
        /// Transitions the framework element, or its nearest parent control with a state of the required name,
        /// to a new state.
        /// </summary>
        /// <param name="frameworkElement">
        /// The framework element the state change was triggered from.
        /// </param>
        /// <param name="stateName">
        /// The name of the visual state to transition to.
        /// </param>
        public static void GoToState(FrameworkElement frameworkElement, string stateName)
        {
            var visualStateInfo = LocateVisualState(frameworkElement, stateName);

            if (visualStateInfo != null)
            {
                instance.GoToStateCore(visualStateInfo.Control, visualStateInfo.FrameworkElement, stateName, visualStateInfo.VisualStateGroup, visualStateInfo.VisualState, true);
            }
        }

        #endregion

        #region Methods

        /// <summary>
        /// Locates the visual state with the given name, relative to the given framework element.
        /// </summary>
        /// <param name="frameworkElement">
        /// The framework element at which to start searching for the visual state.
        /// </param>
        /// <param name="stateName">
        /// The name of the visual state to search for.
        /// </param>
        /// <returns>
        /// The located visual state information.
        /// </returns>
        private static VisualStateInfo LocateVisualState(FrameworkElement frameworkElement, string stateName)
        {
            DependencyObject currentObject = frameworkElement;
            VisualStateInfo result = null;

            // Search up the visual tree until we find a framework element that contains a visual state with the
            // right name.
            do
            {
                var element = currentObject as FrameworkElement;
                if (element != null)
                {
                    result = (from g in VisualStateManager.GetVisualStateGroups(element)
                              let s = g.States.FirstOrDefault(s => s.Name.Equals(stateName, StringComparison.OrdinalIgnoreCase))
                              where s != null
                              select new VisualStateInfo(element, g, s)).FirstOrDefault();
                }

                currentObject = VisualTreeHelper.GetParent(currentObject);
            }
            while (result == null && currentObject != null);

            if (result != null)
            {
                // Found a visual state; keep searching up the tree until we find the closest parent control. 
                do
                {
                    var control = currentObject as Control;
                    if (control != null)
                    {
                        result.Control = control;
                        return result;
                    }

                    currentObject = VisualTreeHelper.GetParent(currentObject);
                }
                while (currentObject != null);
            }
            return null;
        }

        #endregion

        /// <summary>
        /// Information about a matched <see cref="VisualState"/>, including its container
        /// <see cref="VisualStateGroup"/> and associated <see cref="Control"/>.
        /// </summary>
        private class VisualStateInfo
        {
            #region Constructors and Destructors

            /// <summary>
            /// Initializes a new instance of the <see cref="VisualStateInfo"/> class.
            /// </summary>
            /// <param name="frameworkElement">
            /// The framework element.
            /// </param>
            /// <param name="group">
            /// The group.
            /// </param>
            /// <param name="visualState">
            /// The visual state.
            /// </param>
            public VisualStateInfo(FrameworkElement frameworkElement, VisualStateGroup group, VisualState visualState)
            {
                this.FrameworkElement = frameworkElement;
                this.VisualStateGroup = group;
                this.VisualState = visualState;
            }

            #endregion

            #region Public Properties

            /// <summary>
            /// Gets or sets the associated control.
            /// </summary>
            public Control Control { get; set; }

            /// <summary>
            /// Gets the framework element the visual state group was located on.
            /// </summary>
            public FrameworkElement FrameworkElement { get; set; }

            /// <summary>
            /// Gets the matched visual state.
            /// </summary>
            public VisualState VisualState { get; private set; }

            /// <summary>
            /// Gets the visual state group the visual state is contained in.
            /// </summary>
            public VisualStateGroup VisualStateGroup { get; private set; }

            #endregion
        }
    }
}